package cn.xuqiudong.rpc.extension;

import cn.xuqiudong.rpc.common.annotathion.InjectExtension;
import cn.xuqiudong.rpc.common.annotathion.XqdExtension;
import cn.xuqiudong.rpc.common.constants.XqdConstants;
import cn.xuqiudong.rpc.common.util.ClassHelper;
import cn.xuqiudong.rpc.common.util.XqdClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.xuqiudong.rpc.cluster.exception.XqdCommonException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 描述: 自定义扩展组件加载器, 根据实际需要实例化某个name对应的扩展点，或者获取默认扩展点(每个可扩展接口都须被{@link XqdExtension}注解标识) <br/>
 *
 *     由于java 的SPI  ServiceLoader会实例化配置的全部的class，顾定义此加载器<br/>
 *     <p>
 *         自定义扩展点的方式：
 *         在META-INF/xqd 目录下，创建接口的同名文件
 *         文件的每行内容为扩展点name=扩展点借楼实现类的全路径
 *     </p>
 *     https://www.jianshu.com/p/a035cf58db62
 * @author Vic.xu
 * @date 2022-02-14 10:55
 */
public class XqdSpiExtensionLoader<T> {

    private static final Logger logger = LoggerFactory.getLogger(XqdSpiExtensionLoader.class);

    /**
     * 缓存扩展点Loader实例的map, 因为每个类型对应一个XqdExtensionLoade
     */
    private static final ConcurrentMap<Class<?>, XqdSpiExtensionLoader<?>> LOADERS = new ConcurrentHashMap<>(64);

    /**
     * 当前类型扩展点已经加载的class
     */
    private final ConcurrentMap<String, ClassHelper<Object>> cachedInstances = new ConcurrentHashMap<>();

    /**
     * 扩展点实例缓存
     */
    private static final ConcurrentMap<Class<?>, Object> XQD_EXTENSION_INSTANCES_CACHE = new ConcurrentHashMap<>(64);

    /**
     * 当前扩展点的接口类型
     */
    private Class<T> type;

    /**
     * 当前扩展点的默认实现类名称
     */
    private String defaultExtensionName;
    /**
     * 持有当前类型扩展点的全部实现class，  形如name=Class
     */
    private volatile ClassHelper<Map<String, Class<?>>> cachedClassesHelper = new ClassHelper<>();

    /**
     * 获取扩展点的工厂
     */
    private XqdExtensionFactory xqdExtensionFactory;

    /**
     * 缓存的扩展点的指定的name
     */
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();


    public XqdSpiExtensionLoader(Class<T> type) {
        this.type = type;
        //暂时只通过SpiXqdExtensionFactory 获取扩展点，或其应考虑其他方式，如从spring上下文中获取扩展点
        this.xqdExtensionFactory = XqdExtensionFactory.defaultExtendFactory();
    }

    /**
     * 获取某个接口类型的组件加载器实例
     * @param type
     * @param <T>
     * @return
     */
    public static <T> XqdSpiExtensionLoader<T> getXqdExtensionLoader(Class<T> type) {
        // type 不可为空
        if (type == null) {
            throw new XqdCommonException("ExtensionLoader type is null");
        }

        //type 必须是接口
        if (!type.isInterface()) {
            throw new XqdCommonException("ExtensionLoader type(" + type + ") must ben an interface");
        }

        //接口是否被标注为@XqdExtension
        if (!isXqdExtensionAnnotation(type)) {
            throw new XqdCommonException("ExtensionLoader type(" + type + ") must be  annotation with @" + XqdExtension.class.getSimpleName());
        }
        //先从缓存中获取，获取不到则new
        XqdSpiExtensionLoader<T> loader = (XqdSpiExtensionLoader<T>) LOADERS.get(type);
        if (loader == null) {
            loader = new XqdSpiExtensionLoader<>(type);
            LOADERS.putIfAbsent(type, loader);
        }
        return loader;

    }

    /**
     * 获取扩展类的实例
     * @param name
     * @return
     */
    public T getXqdExtension(String name) {
        if (StringUtils.isBlank(name)) {
            throw new XqdCommonException("Extension name is null");
        }
        if ("true".equals(name)) {
            return getDefaultXqdExtension();
        }
        final ClassHelper<Object> holder = getOrCreateCache(name);

        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtensionInstance(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    /**
     * 创建扩展点的实例
     * @param name
     * @return
     */
    private Object createExtensionInstance(String name) {
        Class<?> clazz = findXqdExtensionClassMap().get(name);
        if (clazz == null) {
            throw new XqdCommonException("Non-existent extension point name:" + name);
        }
        try {

            //先从缓存中获取实例
            T instance = (T) XQD_EXTENSION_INSTANCES_CACHE.get(name);
            if (instance == null) {
                instance = (T) clazz.newInstance();
                XQD_EXTENSION_INSTANCES_CACHE.putIfAbsent(clazz, instance);
            }

            //注入其他扩展点，如果需要的话
            injectExtension(instance);

            return instance;
        } catch (Exception e) {
            throw new XqdCommonException(name + " instantiation extension failed, class: " + clazz.getName(), e);
        }


    }

    /**
     * 为实例的属性注入其他扩展点， 属性需要被@InjectExtension注解 {@link InjectExtension}
     * @param instance 实例
     */
    private void injectExtension(T instance) {
        if (xqdExtensionFactory == null) {
            return;
        }
        for (Field field : instance.getClass().getDeclaredFields()) {
            if (!field.isAnnotationPresent(InjectExtension.class)) {
                //非 @InjectExtension注解的字段 不处理
                continue;
            }
            InjectExtension injectExtension = field.getAnnotation(InjectExtension.class);
            Class fileClass = field.getType().getClass();
            String name = injectExtension.value();
            Object xqdExtension = xqdExtensionFactory.getXqdExtension(fileClass, name);
            if (xqdExtension != null) {
                field.setAccessible(true);
                try {
                    field.set(instance, xqdExtension);
                } catch (IllegalAccessException e) {
                    logger.error(instance.getClass() + "为属性" + field.getName() + "注入失败", e);
                }
            }


        }
    }


    /**
     * 根据扩展点名称获取或创建缓存helper
     * @param name
     * @return
     */
    private ClassHelper<Object> getOrCreateCache(String name) {
        ClassHelper<Object> helper = cachedInstances.get(name);
        if (helper == null) {
            cachedInstances.putIfAbsent(name, new ClassHelper<>());
            helper = cachedInstances.get(name);
        }
        return helper;
    }

    private T getDefaultXqdExtension() {
        if (StringUtils.isBlank(defaultExtensionName) || "true".equals(defaultExtensionName)) {
            return null;
        }
        return getXqdExtension(defaultExtensionName);
    }


    /**
     * 获取当前类型扩展点的name=class的map集合
     */
    private Map<String, Class<?>> findXqdExtensionClassMap() {
        Map<String, Class<?>> classes = cachedClassesHelper.get();
        if (classes == null) {
            synchronized (cachedClassesHelper) {
                classes = cachedClassesHelper.get();
                if (classes == null) {
                    classes = loadXqdExtensionClasses();
                    cachedClassesHelper.set(classes);
                }
            }
        }
        return classes;
    }


    /**
     * 从对应文件（META-INF/xqd/接口全限定名）获取当前接口类型的实现类class
     */
    private Map<String, Class<?>> loadXqdExtensionClasses() {
        Map<String, Class<?>> extensionClasses = new HashMap<>();

        String fileName = XqdConstants.SPI_EXTENSION_DIRECTORY + this.type.getName();
        ClassLoader classLoader = XqdClassUtils.class.getClassLoader();
        try {
            Enumeration<URL> urls = null;
            if (classLoader != null) {

                urls = classLoader.getResources(fileName);

            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (IOException e) {
            logger.error(" load extension class (interface: " +
                    type + " error,  file name: " + fileName + ").", e);
        }
        return extensionClasses;
    }

    /**
     * 加载SPI文件中的name对应的class
     * @param extensionClasses
     * @param classLoader
     * @param resourceURL
     */
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    //去掉注释
                    final int commentsIndex = line.indexOf('#');
                    if (commentsIndex >= 0) {
                        line = line.substring(0, commentsIndex);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //加载class，但是并不实例化
                                Class<?> clazz = Class.forName(line, true, classLoader);
                                if (!cachedNames.containsKey(name)) {
                                    cachedNames.put(clazz, name);
                                }
                                Class<?> c = extensionClasses.get(name);
                                if (c == null) {
                                    extensionClasses.put(name, clazz);
                                } else if (c != clazz) {
                                    //当存在一个name对应多个class的时候抛出异常
                                    String msg = type.getName() + "存在多个名为" + name + "的扩展";
                                    throw new XqdCommonException(msg);
                                }

                            }
                        } catch (Exception e) {

                            throw new XqdCommonException("loadResource error ", e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("loadResource error, type =  " + type, e);
        }
    }


    /**
     * 接口是否被标注为@XqdExtension
     * @param type
     * @param <T>
     * @return
     */
    private static <T> boolean isXqdExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(XqdExtension.class);
    }


}

